package com.mosai.alignpro.view;

import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Range;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;

import androidx.annotation.NonNull;

import com.mosai.alignpro.util.LogUtils;
import com.mosai.alignpro.util.Utils;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * Created by Rays on 2020/3/13.
 */
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class CameraView extends TextureView {
    private static final String TAG = CameraView.class.getSimpleName();

    private static final String TAG_PREVIEW = "预览";

    private static final SparseIntArray ORIENTATION = new SparseIntArray();
    public static final int REQUEST_CODE = 101;

    static {
        ORIENTATION.append(Surface.ROTATION_0, 90);
        ORIENTATION.append(Surface.ROTATION_90, 0);
        ORIENTATION.append(Surface.ROTATION_180, 270);
        ORIENTATION.append(Surface.ROTATION_270, 180);
    }

    private String mCameraId;
    private Size mPreviewSize;
    private ImageReader mImageReader;
    private CameraDevice mCameraDevice;
    private CameraCaptureSession mCaptureSession;
    private CaptureRequest.Builder mPreviewRequestBuilder;
    private Surface mPreviewSurface;

    private Integer currentAECompensation;
    private Range<Integer> aeCompensationRange;
    private Range<Integer> fpsRange;

    private OnCameraListener onCameraListener;

    public CameraView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        this.setSurfaceTextureListener(new SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                LogUtils.i(TAG, "SurfaceTextureListener onSurfaceTextureAvailable:"
                        + " width: " + width
                        + " height: " + height
                        + " isAvailable: " + isAvailable()
                        + " getWidth: " + getWidth()
                        + " getHeight: " + getHeight()
                );
                openCamera();
            }


            @Override
            public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
                LogUtils.i(TAG, "SurfaceTextureListener onSurfaceTextureSizeChanged:"
                        + " width: " + width
                        + " height: " + height
                );
            }

            @Override
            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                LogUtils.i(TAG, "SurfaceTextureListener onSurfaceTextureDestroyed:");
                return false;
            }

            @Override
            public void onSurfaceTextureUpdated(SurfaceTexture surface) {
//            Utils.logI(TAG, "SurfaceTextureListener onSurfaceTextureUpdated:");
            }
        });
    }

    public void onResume() {
        LogUtils.i(TAG, "onResume: ");
        openCamera();
    }

    public void onPause() {
        LogUtils.i(TAG, "onPause: ");
        closeCamera();
    }

    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode == REQUEST_CODE) {
            for (int grantResult : grantResults) {
                if (grantResult != PackageManager.PERMISSION_GRANTED) {
                    return;
                }
            }
            LogUtils.i(TAG, "onRequestPermissionsResult: " + (Thread.currentThread() == getContext().getMainLooper().getThread()));
//            openCamera();
        }
    }

    public void setBrightness(float rate) {
        if (mCaptureSession == null || aeCompensationRange == null) {
            return;
        }
        int aeCompensation = (int) ((aeCompensationRange.getUpper() - aeCompensationRange.getLower()) * rate) + aeCompensationRange.getLower();
        LogUtils.i(TAG, "setBrightness"
                + " aeCompensation: " + aeCompensation
                + " currentAECompensation: " + currentAECompensation
                + " rate: " + rate
        );
        if (aeCompensation == currentAECompensation) {
            return;
        }
        currentAECompensation = aeCompensation;
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, currentAECompensation);
        repeatPreview();
    }

    private void closeCamera() {
        if (null != mCaptureSession) {
            mCaptureSession.close();
            mCaptureSession = null;
        }
        if (null != mImageReader) {
            mImageReader.close();
            mImageReader = null;
        }
        if (null != mCameraDevice) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
    }

    private void openCamera() {
        if (!isAvailable()) {
            return;
        }
        if (Utils.checkRequestPermission((Activity) getContext(), REQUEST_CODE, Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
            return;
        }
        //获取摄像头的管理者CameraManager
        CameraManager manager = (CameraManager) getContext().getSystemService(Context.CAMERA_SERVICE);
        if (manager == null) {
            return;
        }
        if (TextUtils.isEmpty(mCameraId)) {
            setupCamera(manager, getWidth(), getHeight());
        }
        if (TextUtils.isEmpty(mCameraId)) {
            return;
        }
        //检查权限
        try {
            closeCamera();
            //打开相机，第一个参数指示打开哪个摄像头，第二个参数stateCallback为相机的状态回调接口，第三个参数用来确定Callback在哪个线程执行，为null的话就在当前线程执行
            manager.openCamera(mCameraId, stateCallback, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void setupCamera(CameraManager manager, int width, int height) {
        try {
            // 遍历所有摄像头
            for (String cameraId : manager.getCameraIdList()) {
                CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
                // 默认打开后置摄像头 - 忽略前置摄像头
                Integer integer = characteristics.get(CameraCharacteristics.LENS_FACING);
                if (integer == null || integer == CameraCharacteristics.LENS_FACING_FRONT) {
                    continue;
                }
                // 获取StreamConfigurationMap，它是管理摄像头支持的所有输出格式和尺寸
                StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                if (map == null) {
                    continue;
                }
                mPreviewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height);
                LogUtils.i(TAG, "setupCamera: mPreviewSize: " + mPreviewSize);

                aeCompensationRange = characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE);
                LogUtils.i(TAG, "setupCamera: aeCompensationRange: " + aeCompensationRange);

                Range<Integer>[] ranges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
                if (ranges != null) {
                    for (Range<Integer> range : ranges) {
                        //帧率不能太低，大于10
                        if (range.getLower() < 10)
                            continue;
                        if (fpsRange == null)
                            fpsRange = range;
                            //FPS下限小于15，弱光时能保证足够曝光时间，提高亮度。range范围跨度越大越好，光源足够时FPS较高，预览更流畅，光源不够时FPS较低，亮度更好。
                        else if (range.getLower() <= 15 && (range.getUpper() - range.getLower()) > (fpsRange.getUpper() - fpsRange.getLower()))
                            fpsRange = range;
                    }
                    LogUtils.i(TAG, "setupCamera:"
                            + " ranges: " + Arrays.toString(ranges)
                            + " result: " + fpsRange
                    );
                }

                mCameraId = cameraId;
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 选择sizeMap中大于并且最接近width和height的size
    private Size getOptimalSize(Size[] sizeMap, int width, int height) {
        LogUtils.i(TAG, "getOptimalSize:"
                + " width: " + width
                + " height: " + height
                + " sizeMap: " + Arrays.toString(sizeMap)
        );
        List<Size> sizeList = new ArrayList<>();
        for (Size option : sizeMap) {
            if (width > height) {
                if (option.getWidth() > width && option.getHeight() > height) {
                    sizeList.add(option);
                }
            } else {
                if (option.getWidth() > height && option.getHeight() > width) {
                    sizeList.add(option);
                }
            }
        }
        if (sizeList.size() > 0) {
            return Collections.min(sizeList, new Comparator<Size>() {
                @Override
                public int compare(Size lhs, Size rhs) {
                    return Long.signum(lhs.getWidth() * lhs.getHeight() - rhs.getWidth() * rhs.getHeight());
                }
            });
        }
        return sizeMap[0];
    }

    // 摄像头状态回调
    private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            LogUtils.i(TAG, "CameraDevice.StateCallback onOpened:");
            mCameraDevice = camera;
            //开启预览
            startPreview();
        }


        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            LogUtils.i(TAG, "CameraDevice.StateCallback Disconnected");
            camera.close();
            mCameraDevice = null;
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            LogUtils.e(TAG, "CameraDevice.StateCallback Error " + error);
        }
    };

    private void startPreview() {
        setupImageReader();
        SurfaceTexture mSurfaceTexture = this.getSurfaceTexture();
        //设置TextureView的缓冲区大小
        mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        //获取Surface显示预览数据
        mPreviewSurface = new Surface(mSurfaceTexture);
        try {
            getPreviewRequestBuilder();
            //创建相机捕获会话，第一个参数是捕获数据的输出Surface列表，第二个参数是CameraCaptureSession的状态回调接口，当它创建好后会回调onConfigured方法，第三个参数用来确定Callback在哪个线程执行，为null的话就在当前线程执行
            mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    LogUtils.i(TAG, "CameraCaptureSession StateCallback onConfigured:");
                    mCaptureSession = session;
                    repeatPreview();
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                    LogUtils.i(TAG, "CameraCaptureSession StateCallback onConfigureFailed:");
                }
            }, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void setupImageReader() {
        //前三个参数分别是需要的尺寸和格式，最后一个参数代表每次最多获取几帧数据
        mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.JPEG, 1);
        //监听ImageReader的事件，当有图像流数据可用时会回调onImageAvailable方法，它的参数就是预览帧数据，可以对这帧数据进行处理
        mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                LogUtils.i(TAG, "Image Available!");
                Image image = reader.acquireLatestImage();
                // 开启线程异步保存图片
                new Thread(new ImageSaver(image, getContext(), onCameraListener)).start();
            }
        }, null);
    }

    // 创建预览请求的Builder（TEMPLATE_PREVIEW表示预览请求）
    private void getPreviewRequestBuilder() {
        try {
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //设置预览的显示界面
        mPreviewRequestBuilder.addTarget(mPreviewSurface);
        MeteringRectangle[] meteringRectangles = mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AF_REGIONS);
        if (meteringRectangles != null && meteringRectangles.length > 0) {
            LogUtils.i(TAG, "PreviewRequestBuilder: AF_REGIONS=" + meteringRectangles[0].getRect().toString());
        }
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
        if (currentAECompensation != null) {
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, currentAECompensation);
        } else {
            currentAECompensation = mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION);
            if (currentAECompensation == null && aeCompensationRange != null) {
                currentAECompensation = (aeCompensationRange.getUpper() - aeCompensationRange.getLower()) / 2;
            }
        }
        LogUtils.i(TAG, "PreviewRequestBuilder: currentCompensation: " + currentAECompensation);
        Range<Integer> integerRange = mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
        LogUtils.i(TAG, "PreviewRequestBuilder: fpsRange: " + integerRange);
        if (fpsRange != null) {
            // 自动对焦
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            // 设置自动曝光帧率范围
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
            //对焦触发器设置为空闲状态
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
        }
    }

    private void repeatPreview() {
        //设置反复捕获数据的请求，这样预览界面就会一直有数据显示
        try {
            mPreviewRequestBuilder.setTag(TAG_PREVIEW);
            mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 拍照
    public void capture(Activity activity) {
        try {
            //首先我们创建请求拍照的CaptureRequest
            final CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);

            mCaptureBuilder.addTarget(mPreviewSurface);
            mCaptureBuilder.addTarget(mImageReader.getSurface());

            //获取屏幕方向
            int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
            //设置拍照方向
            LogUtils.i(TAG, "capture rotation: " + rotation + " ORIENTATION: " + ORIENTATION.get(rotation));
            mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));

            //停止预览
            mCaptureSession.stopRepeating();

            //开始拍照，然后回调上面的接口重启预览，因为mCaptureBuilder设置ImageReader作为target，所以会自动回调ImageReader的onImageAvailable()方法保存图片
            CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {

                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                    repeatPreview();
                }
            };

            mCaptureSession.capture(mCaptureBuilder.build(), captureCallback, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static class ImageSaver implements Runnable {
        private Image mImage;
        private Context context;
        private OnCameraListener onCameraListener;

        ImageSaver(Image image, Context context, OnCameraListener onCameraListener) {
            mImage = image;
            this.context = context;
            this.onCameraListener = onCameraListener;
        }

        @Override
        public void run() {
            Image.Plane[] planes = mImage.getPlanes();
            LogUtils.i(TAG, "run: " + planes.length);
            if (planes.length <= 0) {
                return;
            }
            ByteBuffer buffer = planes[0].getBuffer();
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            mImage.close();
            Utils.saveImage(context, data, (path, imgUri) -> {
                if (onCameraListener != null && imgUri != null) {
                    onCameraListener.onCamera(imgUri.toString());
                }
            });
        }
    }

    public void setOnCameraListener(OnCameraListener onCameraListener) {
        this.onCameraListener = onCameraListener;
    }

    public interface OnCameraListener {
        void onCamera(String uri);
    }

}
